feat(draft): whitelist names before mainnet - #239
Conversation
CI Summary
4naly3er AnalysisMedium (5)
Low (10)
Gas (14)
Informational (11)
Slither AnalysisHigh (3)
Medium (44)
Low (56)
Informational (63)
Contract Tests (Unit + Fuzz)BasicDotnsIntegrationReverts (test/intergration/BasicDotns.reverts.t.sol)
BasicDotnsIntegration (test/intergration/BasicDotns.t.sol)
DeployCreate3FactoryTest (test/unit/deploy/DeployCreate3Factory.t.sol)
DeterministicDeploymentTest (test/unit/deploy/DeterministicDeployment.t.sol)
DotnsContentResolverTests (test/unit/resolver/DotnsContentResolver.t.sol)
DotnsNameEscrowTest (test/unit/escrow/DotnsNameEscrow.t.sol)
DotnsNameEscrowFuzzTest (test/fuzz/escrow/DotnsNameEscrowFuzz.t.sol)
DotnsNameEscrowRefundsTest (test/unit/escrow/DotnsNameEscrowRefunds.t.sol)
DotnsNameWhitelistTests (test/unit/whitelist/DotnsNameWhitelist.t.sol)
DotnsNameWhitelistFuzz (test/fuzz/whitelist/DotnsNameWhitelistFuzz.t.sol)
DotnsPopControllerTests (test/unit/registrar/DotnsPopController.t.sol)
DotnsPopControllerFuzz (test/fuzz/registrar/DotnsPopControllerFuzz.t.sol)
DotnsPopResolverTests (test/unit/resolver/DotnsPopResolver.t.sol)
DotnsProtocolRegistryTldTests (test/unit/registry/DotnsProtocolRegistry.t.sol)
DotnsProtocolRegistryFuzzTest (test/fuzz/registry/DotnsProtocolRegistryFuzz.t.sol)
DotnsRegistrarTests (test/unit/registrar/DotnsRegistrar.t.sol)
DotnsRegistrarControllerTest (test/unit/registrar/DotnsRegistrarController.t.sol)
DotnsRegistrarControllerFuzzTest (test/fuzz/registrar/DotnsRegistrarControllerFuzz.t.sol)
DotnsRegistrarControllerLifecycleTest (test/unit/registrar/DotnsRegistrarControllerLifecycle.t.sol)
DotnsRegistryTests (test/unit/registry/DotnsRegistry.t.sol)
DotnsRegistryFuzzTest (test/fuzz/registry/DotnsRegistryFuzz.t.sol)
DotnsResolverTests (test/unit/resolver/DotnsResolver.t.sol)
DotnsReverseResolverTests (test/unit/resolver/DotnsReverseResolver.t.sol)
LabelStoreTests (test/unit/store/LabelStore.t.sol)
LabelStoreFuzzTest (test/fuzz/store/LabelStoreFuzz.t.sol)
NoStatusDepositLifecycle (test/intergration/NoStatusDepositLifecycle.t.sol)
PopRulesFuzzTest (test/fuzz/pop/PopFuzz.t.sol)
PopLifecycleFlow (test/intergration/PopLifecycleFlow.t.sol)
PopRulesTests (test/unit/pop/PopRules.t.sol)
RootGatewayDispatcherTests (test/unit/registrar/RootGatewayDispatcher.t.sol)
StoreFactoryTests (test/unit/store/StoreFactory.t.sol)
StoreIntegrationTest (test/intergration/StoreIntegration.t.sol)
StoreStressTest (test/stress/store/StoreStress.t.sol)
UserStoreTests (test/unit/store/UserStore.t.sol)
UserStoreFuzzTest (test/fuzz/store/UserStoreFuzz.t.sol)
WhitelistOperatorFlow (test/intergration/WhitelistOperatorFlow.t.sol)
Contract Tests (Invariant)DotnsNameEscrowInvariantTest (test/invariant/escrow/DotnsNameEscrowInvariant.t.sol)
DotnsNameWhitelistInvariant (test/invariant/whitelist/DotnsNameWhitelistInvariant.t.sol)
DotnsPopControllerInvariant (test/invariant/registrar/DotnsPopControllerInvariant.t.sol)
DotnsRegistrarControllerInvariantTest (test/invariant/registrar/DotnsRegistrarControllerInvariant.t.sol)
DotnsRegistrarControllerRoleInvariantTest (test/invariant/registrar/DotnsRegistrarControllerRoleInvariant.t.sol)
DotnsRegistryInvariantTest (test/invariant/registry/DotnsRegistryInvariant.t.sol)
StoreInvariantTest (test/invariant/store/StoreInvariant.t.sol)
Coverage
Deploy ContractsDeployed addresses vs DEPLOYMENTS.mdExpected is the committed manifest; actual is this CI deployment of the same pipeline.
Labelssmartcontracts, other, type: test Gas ReportDotnsProtocolRegistry
DotnsRegistrar
DotnsRegistrarController
DotnsRegistry
DotnsReverseResolver
PopRules
StoreFactory
|
re-gius
left a comment
There was a problem hiding this comment.
The contract itself is in a good state.
My comments are all about the authority model, and I think they reduce to one decision rather than a list of fixes. This is built for dev-operated pre-launch allocation, and it does that well. Mainnet needs governance-only allocation, and the contract is currently the opposite of that by construction: _checkRoleOrOwner passes for owner(), so the deployer is a first-class allocator on every path, and _authorizeUpgrade is owner-held, so any gate added later can be replaced in two transactions. A referendum, meanwhile, can't use those paths at all since it arrives as Root, which has no msg.sender.
My suggestion is adding a one-way lockToGovernance(): keep today's owner and operator paths for pre-launch, and have the lock permanently switch every allocation path to ISystem(REVIVE_SYSTEM).originIsRoot() and kill the owner and role branches.
You may also go with the current version, but we should keep in mind this is not ready for mainnet launch and should be upgraded for mainnet in a follow up (as established in #205).
| function _validateNew(string calldata label) internal view returns (bytes32 node) { | ||
| require(label.isSingleLabel(), InvalidLabel()); | ||
| node = _nodeOf(label); | ||
| require(_grants[node].status == GrantStatus.None, AlreadyExists(node)); |
There was a problem hiding this comment.
Any live entry blocks grantName, including a Requested one written by anybody, and requestName is permissionless and free beyond gas. So a malicious actor can force every desirable label into a state where allocation needs a paired revokeName first.
Suggested fix: let grantName overwrite any non-Accepted entry. That keeps Accepted protected so one grant can't silently overwrite another, preserves sticky rejection as a default against re-requests, and lets governance deliberately allocate over a blocked label in a single call.
There was a problem hiding this comment.
The redesign fixes this. Pending claims no longer block grantName. A name stays Open while it has pending claims, so a direct grant clears them and emits NameRejected for each one, so the audit trail you wanted is there.
grantName is now blocked only when a name is Reserved (governance reserved it) or Claimed (someone already won it), which is the Accepted protection you asked for. Governance can allocate over a squatted label in one call, with events, and no paired revokeName.
| uint256[50] private __gap; | ||
|
|
||
| /// @notice Restricts a call to an operator or the owner. | ||
| modifier onlyOperatorOrOwner() { |
There was a problem hiding this comment.
_checkRoleOrOwner passes for caller == owner(), and initialize (line 84) sets the owner to msg.sender. So the deployer can allocate any name to any address with no referendum, which is not allowed on mainnet.
Notice that a referendum cannot use this path at all: it arrives as Root, and Root has no msg.sender. Since this contract sits behind a UUPS proxy, the check has to be ISystem(REVIVE_SYSTEM).originIsRoot(), the same reason the PoP controller needs it.
However, I'd suggest keeping these paths for devnet and adding an irreversible lockToGovernance() that permanently switches every allocation path to the Root gate and kills the owner/role branches. That preserves pre-launch ergonomics and makes the mainnet posture verifiable on-chain.
There was a problem hiding this comment.
Root works on this path now. Both governance modifiers check SystemUtils.originIsRoot() before reading msg.sender, so a referendum coming in as Root can allocate even though it has no address.
This contract is reusable, mainnet included, and Root is the authority there. The owner branch is the same Root-or-owner model every contract uses. Whether we keep or drop the owner side is decided in #238, uniformly across all contracts, not with a per-contract lockToGovernance.
| } | ||
|
|
||
| /// @notice Restricts upgrades to the owner. | ||
| function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} |
There was a problem hiding this comment.
maybe we should reserve this for governance only? Again, if we allow an irreversible lockToGovernance(), this may only change afterwards
There was a problem hiding this comment.
_authorizeUpgrade stays owner-gated for now. Moving upgrade authority to the Root gate is the same #238 change across every upgradeable contract, mainnet included, not a per-contract lock.
|
This isn't only a pre-launch tool. It's a reusable governance-gated allocation contract, and the Root gate is what makes it work on mainnet. The authority model is Root-or-owner, decided uniformly in #238 across every contract rather than a per-contract |
Description
Adds a name whitelist that binds a label to the beneficiary governance permits to register it. A name is Open until governance reserves it or accepts a claim on it. Anyone can claim an Open name within the request window, each with a reason, and several beneficiaries can claim the same name. Governance accepts one as the winner or grants a name directly. Claims are keyed by the beneficiary, not the submitter, so a relayer or a cross-chain sovereign account can claim on a user's behalf.
Governance is Root or the owner. Both governance gates check
SystemUtils.originIsRoot()before readingmsg.sender, since Substrate Root has no address. Governance sets the request window and tunes the per-name claim cap, reason byte cap and grant-batch cap within the ceilings inDotnsConstants. Operators run day-to-day accept, reject and grant.The whitelist exposes
granteeOffor a registrar controller to check an allocation and a controller-onlyconsumehook to clear it at registration. All state is on-chain and queryable through paginated views, so no event indexing is needed. Resolving a name deletes its claims and refunds their storage deposit, so only reserved or won names persist.It also adds
SystemUtils.originIsRootas the shared revive System precompile check, theNAME_WHITELISTregistry key inDotnsConstants, and unit, fuzz and invariant coverage. The deployment script deploys the whitelist and registers it on the protocol registry.Type
Scope
Related Issues
#238 tracks moving allocation and upgrade authority to the Root gate uniformly across every contract. #205 tracks the mainnet governance model. #240 tracks the claim reason field.
Fixes
Fixes #203 .
Checklist
Code
forge buildpassesforge testpassesTesting
Security
selfdestructordelegatecallDocumentation
Breaking Changes
Breaking changes:
How to test
Notes
Allocation runs through one Root-or-owner path across every network. Removing the owner branch and moving
_authorizeUpgradeto the Root gate is the uniform change tracked in #238.